package de.iritgo.skillfull.collision;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import de.iritgo.skillfull.component.CollisionComponent;

/**
 * Quad node. Calculate and manipulate the tree. It holds all collision components
 * 
 * @author held
 */
public class QuadNode implements MoveListener
{
	/** The list with collision components in that node */
	private List<CollisionComponent> collisionComponents = new LinkedList<CollisionComponent>();

	/** The 4 sub nodes */
	private QuadNode[] quadNodes;
	
	/** The depth level */
	private int depthLevel;
	
	/** Indicates that this node is splitted in 4 sub nodes */
	private boolean split;
	
	/** The parent node */
	private QuadNode parent;

	/** Reference to the node list in the quadtree class */
	private List<QuadNode> nodes;
	
	/** The rectangle for the node position and with, height */
	private Rectangle rectangle;
	
	public int getX ()
	{
		return rectangle.x;
	}

	public int getY ()
	{
		return rectangle.y;
	}


	public int getWidth() 
	{
		return rectangle.width;
	}
	
	public int getHeight() 
	{
		return rectangle.height;
	}

	public List<CollisionComponent> getCollisionComponents() 
	{
		return collisionComponents;
	}

	public boolean isSplit ()
	{
		return split;
	}

	public int size ()
	{
		return collisionComponents.size ();
	}

	public int getDepthLevel() 
	{
		return depthLevel;
	}

	public QuadNode[] getQuadNodes ()
	{
		return quadNodes;
	}

	/**
	 * Get the parent node
	 * 
	 * @return The parent node or null
	 */
	public QuadNode getParent() 
	{
		if (parent == null)
		{
			return this;
		}
		return parent;
	}

	/**
	 * Create a space within the quad tree
	 * @param x The x position
	 * @param y The x position
	 * @param width The width
	 * @param height The height
	 * @param depthLevel The depth leven
	 * @param parent The parent node
	 * @param nodes The quadtree node list reference
	 */
	public QuadNode (int x, int y, int width, int height, int depthLevel, QuadNode parent, List<QuadNode> nodes) 
	{
		this.depthLevel = depthLevel;
		this.collisionComponents = new ArrayList<CollisionComponent>();
		this.parent = parent;
		split = false;
		this.nodes = nodes;
		rectangle = new Rectangle (x,y,width,height);
	}
	
	/**
	 * Add a collision component to the node
	 * 
	 * @param CollisionComponent The cc to add to the node
	 */
	public void addCollisionComponent (CollisionComponent cc) 
	{
		// Check if the current node is split and delegate the cc do a sub node
		if (isSplit () && getQuadNodes () != null)
		{
			for (QuadNode quadNode : getQuadNodes ()) 
			{
				if (quadNode.touches (cc)) 
				{
					quadNode.addCollisionComponent (cc);
				}
			}
			return;
		}
		// Check we have allready this cc in the node
		if (collisionComponents.contains(cc))
		{
			return;
		}

		// Enough room for the cc?
		if (collisionComponents.size() < QuadTree.MAX_COL_COMPS)
		{
			collisionComponents.add (cc);
			cc.addMoveListener (this);
			return;
		}
		else if (depthLevel < QuadTree.MAX_DEPTH_LEVEL)
		{
			if (createSplitNodes (depthLevel + 1))
			{
				addCollisionComponent (cc);
				return;
			}
				
		}
		// Fall back
		collisionComponents.add (cc);
		cc.addMoveListener (this);
	}
	
	/**
	 * Sub-divide this space into four seperate sub-spaces dolling
	 * out the bodies into each space
	 * 
	 * @return The spaces created by the subdivision (always length 4)
	 */
	public boolean createSplitNodes (int depthLevel) 
	{
		if (rectangle.width < QuadTree.MIN_QUAD_SIZE && rectangle.height < QuadTree.MIN_QUAD_SIZE)
		{											   
			return false;
		}
		
		int width = rectangle.width / 2;
		int height = rectangle.height / 2;
		
		quadNodes = new QuadNode[4];
		quadNodes[0] = new QuadNode(rectangle.x, rectangle.y, width, height, depthLevel, this, nodes);
		nodes.add (quadNodes[0]);
		quadNodes[1] = new QuadNode(rectangle.x,rectangle.y+height, width, height, depthLevel, this, nodes);
		nodes.add (quadNodes[1]);
		quadNodes[2] = new QuadNode(rectangle.x+width,rectangle.y,width,height, depthLevel, this, nodes);
		nodes.add (quadNodes[2]);
		quadNodes[3] = new QuadNode(rectangle.x+width,rectangle.y+height,width,height, depthLevel, this, nodes);
		nodes.add (quadNodes[3]);
		split = true;
		
		for (CollisionComponent cc : collisionComponents) 
		{
			for (int j=0;j<4;j++) 
			{
				if (quadNodes[j].touches (cc)) 
				{
					quadNodes[j].addCollisionComponent (cc);
				}
			}
		}
		// Clean all nodes from this splitted node and remove the move listeners.
		for (CollisionComponent cc : collisionComponents) 
		{
			cc.removeMoveListener (this);
		}
		collisionComponents.clear ();
		return true;
	}

	/**
	 * Check if this space touches a box or get out of the main rectangle
	 * 
	 * @param cc The box to check against
	 * @return true if the cc collide
	 */
	public boolean touches (CollisionComponent other) 
	{
		if (other.getX () < -256 || other.getX () + other.getWidth () > 1280 * QuadTree.MULTI ||
			other.getY () < -256 || other.getY () + other.getHeight () > 1024 * QuadTree.MULTI
				)
		{
			System.out.println ("OOOOHHHHHH NOOO, thats not good!!");
			return true;
		}
		if (! rectangle.contains (other.getRectangle()))
		{	
			return rectangle.intersects(other.getRectangle());
		}
		return true;
	}

	/**
	 * Remove a cc from the list.
	 * 
	 * @param collisionComponent
	 */
	public void remove (CollisionComponent collisionComponent) 
	{
		collisionComponents.remove (collisionComponent);
	}

	/**
	 * This method is called from a collision component and inform the tree of the moving
	 * 
	 * @see de.iritgo.skillfull.collision.MoveListener#moving(de.iritgo.skillfull.component.CollisionComponent)
	 */
	@Override
	public void moving (CollisionComponent cc) 
	{
		// The noden contain the cc. do nothing
		if (rectangle.contains (cc.getRectangle()))
			return;
		
		if (parent != null)
		{
			// If the cc dont intersects the node than remove the cc completely
			if (! rectangle.intersects (cc.getRectangle()))
			{
				collisionComponents.remove (cc);
				cc.removeMoveListener (this);
			}
			// Sort the cc in the node tree
			parent.collisionComponentLeaveNode (this, cc);
			return;
		}
	}

	/**
	 * Sort the cc in the tree
	 * 
	 * @param quadNode The child node
	 * @param cc The cc
	 */
	private void collisionComponentLeaveNode (QuadNode quadNode,
			CollisionComponent cc) 
	{
		// Move it to a another sub child?!
		if (quadNodes != null)
		{
			for (int j=0;j<4;j++) 
			{
				if (quadNode == quadNodes[j])
					continue;
				
				if (quadNodes[j].touches (cc)) 
				{
					quadNodes[j].addCollisionComponent (cc);
				}
			}
		}
		// Check adjoining nodes
		int size = 0;
		int splits = 0;
		if (quadNodes != null)
		{
			for (int j=0;j<4;j++) 
			{
				size += quadNodes[j].size ();
				splits +=  + (quadNodes[j].isSplit() ? 1 : 0);
			}
			if (size < QuadTree.MAX_COL_COMPS && splits == 0)
			{
				split = false;
				for (int j=0;j<4;j++) 
				{
					for (CollisionComponent upCc : quadNodes[j].getCollisionComponents ())
					{
						if (! collisionComponents.contains (upCc))
						{
							upCc.removeMoveListener(quadNodes[j]);
							upCc.addMoveListener(this);
							collisionComponents.add(upCc);
						}
					}
					nodes.remove(quadNodes[j]);
					quadNodes[j] = null;
				}
				
				quadNodes = null;
			}
		}
		
		if (! rectangle.contains(cc.getRectangle()))
		{
			if (parent != null)
			{
				parent.collisionComponentLeaveNode (this, cc);
			}
		}
	}
}
